#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

using PdfCreative.Pdf.Writer.Fonts.Type1;

namespace PdfCreative.Pdf.Writer.Fonts
{
   /// <summary>
    /// The Type1FontInfo class represent a Type 1 font file that is loaded from type 1 definition files.
    /// The Type1FontInfo class gets its data from the font outlines file and the metrics file of a Type-1
    /// font.
    /// It contains properties on the font, and a list of glyph properties and measurements.
    /// </summary>
    public class Type1FontInfo : FontInfo
    {
        // Private fields
        // ==============
        #region Properties
        private int _ascent;                    // The font's ascent
        private int _charSet;                   // The font's character set
        private int _capHeight;                 // The height of capitals
        private int _descent;                   // The font's descent
        private bool _isItalic;                 // Is this font italic?
        private int _maximumWidth;              // The maximum width of the font
        private int _minimumWidth;              // The minimum width of the font
        private int _pitchAndFamily;            // The font's classification
        private int _slant;                     // The font's oblique slant
        #endregion

        #region State
        private long _extentOffset;             // Offset in the file of the extent data
        private int _firstChar;                 // The first character in the list
        private int _lastChar;                  // The last character in the list
        private long _metricsOffset;            // Offset in the file of the metrics data
        private int[] _characterWidths;         // The Character widths
        #endregion




        // Public overrides
        // ================
        #region FontInfo properties
        /// <summary>
        /// Retrieves the ascent of this font.
        /// </summary>
        public override int Ascent
        {
            get { return _ascent; }
        }

        /// <summary>
        /// Retrieves the height of capitals in this font.
        /// </summary>
        public override int CapHeight
        {
            get { return _capHeight; }
        }

        /// <summary>
        /// Retrieves the descent of this font.
        /// </summary>
        public override int Descent
        {
            get { return _descent; }
        }

        /// <summary>
        /// Retrieves the font's flags as demanded by the PDF specification.
        /// </summary>
        public override int Flags
        {
            get
            {
                int flags = 0;
                // monospace or proportional
                if ((_pitchAndFamily & 1) == 0)
                    flags |= 1;

                // Serif font
                if ((_pitchAndFamily & 16) == 16)
                    flags |= 2;

                // Symbolic font
                if ((_pitchAndFamily & 64) == 64)
                    flags |= 4;

                // Non-symbolic font?
                if (_charSet == 0)
                    flags |= 32;

                // Italic?
                if (_isItalic)
                    flags |= 64;

                return flags;
            }
        } // Flags

        /// <summary>
        /// Retrieves the angle of the font if it's an italic font.
        /// The value is 0 for non-italic fonts.
        /// </summary>
        public override decimal ItalicAngle
        {
            get { return -_slant; }
        }
        /// <summary>
        /// Retrieves the font's postscript name.
        /// </summary>
        public override string PostscriptName
        {
            get { return null; }
        }

        /// <summary>
        /// Retrieves the width of the vertical stems used in this font.
        /// This value is a rough estimate as no information on vertical stems is available in the
        /// PFM file.
        /// </summary>
        public override int StemV
        {
            get
            {
                if (_isItalic)
                    return (int)Math.Round(_minimumWidth * 0.25f);
                else
                    return (int)Math.Round(_minimumWidth * 0.6f);
            }
        }

        /// <summary>
        /// Retrieves the number of units per Em-point that is used in the font file.
        /// </summary>
        public override int UnitsPerEm
        {
            get { return 1000; }
        }

        /// <summary>
        /// Retrieves the maximum X value for the font's bounding box.
        /// This value is the maximum glyph width (the width of the widest glyph)
        /// </summary>
        public override int XMax
        {
            get { return _maximumWidth; }
        }

        /// <summary>
        /// Retrieves the minimum X value for this font's bounding box.
        /// This value is fixed to -100 as the information is not available in the PFM file.
        /// </summary>
        public override int XMin
        {
            get { return -100; }
        }

        /// <summary>
        /// Retrieves the maximum Y value for the font's bounding box.
        /// This value is the same as the value of the Ascent property.
        /// </summary>
        public override int YMax
        {
            get { return _ascent; }
        }

        /// <summary>
        /// Retrieves the minimum Y value for this font's bounding box.
        /// This value is the same as the value of the Descent property.
        /// </summary>
        public override int YMin
        {
            get { return _descent; }
        }

        #endregion

        #region FontInfo methods
        /// <summary>
        /// Retrieves the width of the specified character.
        /// </summary>
        /// <param name="ch">The character to retrieve the width of.</param>
        /// <returns>The width</returns>
        public override int GetCharacterWidth(char ch)
        {
            if (ch >= _firstChar && ch <= _lastChar)
                return _characterWidths[ch - _firstChar];
            else
                return 1000;
        }

        /// <summary>
        /// Parses the font file.
        /// </summary>
        /// <param name="filename">The name of the file to parse</param>
        /// <exception cref="Type1FontParseException">An error occurred while parsing the font file. See the
        /// exception's InnerException for more details, where available.</exception>
        public override void Parse(string filename)
        {
            if (null == filename) throw new ArgumentNullException("filename");

            Parse(File.OpenRead(filename));
        }

        /// <summary>
        /// Parses the data contained in the stream. The stream must support seeking and data will be read from
        /// the start of the stream.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>        
        /// <exception cref="Type1FontParseException">An error occurred while parsing the font file. See the
        /// exception's InnerException for more details, where available.</exception>
        public override void Parse(Stream stream)
        {
            if (null == stream) throw new ArgumentNullException("stream");

            ParseFontStream(stream);
        }

        /// <summary>
        /// Parses the headers of the font file. The requirement for this is that the font's name is read. No
        /// other data is required to be read.
        /// </summary>
        /// <param name="filename">The name of the file to parse</param>
        public override void ParseHeaders(string filename)
        {
            Parse(filename);
        }

        /// <summary>
        /// Parses the data contained in the stream. The stream must support seeking and data will be read from
        /// the start of the stream. Only the font's headers are read.
        /// The requirement for this is that the font's name is read. No
        /// other data is required to be read.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        public override void ParseHeaders(Stream stream)
        {
            Parse(stream);
        }
        #endregion



        // Private implementation
        // ======================
        #region Parsing font data
        /// <summary>
        /// Parses the Type 1 data contained in the stream. 
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        private void ParseFontStream(Stream stream)
        {
            Debug.Assert(null != stream);

            try
            {
                // Rewind the stream.
                stream.Seek(0, SeekOrigin.Begin);

                // Read the PFM file header.
                ReadPfmHeader(stream);

                // Read the PFM extension header. This will give us offsets to the
                // metrics table.
                ReadExtensionHeader(stream);

                // Read the extended metrics table.
                ReadExtendedMetrics(stream);

                // Finally, read the extent table. This will give us the glyph information we need.
                ReadExtentInformation(stream);
            }
            // If an IO exception occurred we rethrow as a Type1FontParseException and include the original exception for reference.
            catch (IOException ex)
            {
                throw new Type1FontParseException("An I/O error occurred while reading the font file", ex);
            }
        } // ParseFontStream()
        #endregion

        #region Reading PFM blocks
        /// <summary>
        /// Reads the extended metrics from the stream. This will give us useful metrics without having to
        /// calculate them from the PFB file.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        private void ReadExtendedMetrics(Stream stream)
        {
            Debug.Assert(null != stream);

            // Seek to the metrics table.
            stream.Seek(_metricsOffset, SeekOrigin.Begin);

            int next = 0;
            next = ReadShortValue(stream);          // size of this structure (should be 52)
            next = ReadShortValue(stream);          // point size
            next = ReadShortValue(stream);          // orientation
            next = ReadShortValue(stream);          // master height
            next = ReadShortValue(stream);          // min. scale
            next = ReadShortValue(stream);          // max. scale
            next = ReadShortValue(stream);          // master units

            _capHeight = ReadShortValue(stream);    // cap height

            next = ReadShortValue(stream);          // x height
            next = ReadShortValue(stream);          // ascent
            _descent = -ReadShortValue(stream);     // descent
            _slant = ReadShortValue(stream);        // slant


        } // ReadExtendedMetrics()

        /// <summary>
        /// Reads the PFM extension header from the stream. This will give us the offset within the file of the
        /// metrics table.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        private void ReadExtensionHeader(Stream stream)
        {
            Debug.Assert(null != stream);

            int size = ReadShortValue(stream);  // size of this structure (should be 30)
            if (30 == size)
            {
                _metricsOffset = ReadLongValue(stream);
                _extentOffset = ReadLongValue(stream);
            }
            else
                throw new Type1FontParseException("The extension header in the font file is invalid");

        } // ReadExtensionHeader()

        /// <summary>
        /// Reads the extents of each of the characters in the range defined in the initial PFM header.
        /// We can treat each of these widths as the advance width.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        private void ReadExtentInformation(Stream stream)
        {
            Debug.Assert(null != stream);

            // Seek to the extent table.
            stream.Seek(_extentOffset, SeekOrigin.Begin);

            // Create a new array of widths. We will read the width of each character and stick it inside the array.
            _characterWidths = new int[_lastChar - _firstChar + 1];

            int width = 0;
            int charIndex = 0;
            _minimumWidth = _maximumWidth;
            for (int ch = _firstChar; ch <= _lastChar; ch++)
            {
                width = ReadShortValue(stream);
                _characterWidths[charIndex++] = width;
                if (width < _minimumWidth) _minimumWidth = width;
            }
        } // ReadExtentInformation()

        /// <summary>
        /// Reads the initial PFM file header.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        private void ReadPfmHeader(Stream stream)
        {
            Debug.Assert(null != stream);

            // Version of the file and length of this header.
            int version = ReadShortValue(stream);
            long length = ReadLongValue(stream);

            // skip the next 60 bytes of copyright messages
            stream.Seek(60, SeekOrigin.Current);

            int next = 0;
            next = ReadShortValue(stream);              // type
            next = ReadShortValue(stream);              // points
            next = ReadShortValue(stream);              // vertical resolution
            next = ReadShortValue(stream);              // horizontal resolution
            
            _ascent = ReadShortValue(stream);           // ascent
            
            next = ReadShortValue(stream);              // internal leading
            next = ReadShortValue(stream);              // external leading
            
            _isItalic = 1 == ReadByte(stream);          // Italic
            
            next = ReadByte(stream);                    // Underline
            next = ReadByte(stream);                    // Strikeout
            next = ReadShortValue(stream);              // Weight
            
            _charSet = ReadByte(stream);                // Character set
            
            next = ReadShortValue(stream);              // Pixel-Width
            next = ReadShortValue(stream);              // Pixel-Height
            
            _pitchAndFamily = ReadByte(stream);         // Pitch and family
            
            next = ReadShortValue(stream);              // Average width
            
            _maximumWidth = ReadShortValue(stream);     // Maximum width

            // Read the first and last character in this font file.
            _firstChar = ReadByte(stream);
            _lastChar = ReadByte(stream);

            if (_firstChar > _lastChar) throw new Type1FontParseException(
                   "The PFM header of this file contains invalid information");

            stream.Seek(20, SeekOrigin.Current);
        } // ReadPfmHeader()
        #endregion

        #region Reading primitive values

        /// <summary>
        /// Reads a BYTE value from the stream.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private int ReadByte(Stream stream)
        {
            return stream.ReadByte();
        }

        /// <summary>
        /// Reads a LONG value from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The LONG value</returns>
        private long ReadLongValue(Stream stream)
        {
            long value = 0;
            value += stream.ReadByte();
            value += stream.ReadByte() << 8;
            value += stream.ReadByte() << 16;
            value += stream.ReadByte() << 24;

            return value;
        }

        /// <summary>
        /// Reads a SHORT value from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The SHORT value</returns>
        private int ReadShortValue(Stream stream)
        {
            short value = (short)stream.ReadByte();
            value += (short)(stream.ReadByte() << 8);
            return value;
        }

        #endregion

    } // Type1FontInfo class
}
